#include "gameserver.h"
#include "message.h"

#include <QDebug>
#include <QMessageBox>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>

#include <cassert>
#include <cstring>
#include <algorithm>

GameServer::GameServer() : tcpServer(nullptr), state(GameState::waiting),
    exiting(false)
{
    resetState();
}

void GameServer::resetState()
{
    // state is not touched here, state "waiting" will be
    // skipped when restarting a game.
    // no need to set currentPlayer, exiting, lastPosted
    numPlayers = 0;
    landlord = -1;
    memset(cardsPosted, -1, sizeof(cardsPosted));
    memset(preempted, -1, sizeof(preempted));

    for (int i = 0; i < maximumPlayers; ++i)
        cards[i].clear();
    remainingCards.clear();
    postedCardsRepr = "";
}

bool GameServer::start(quint16 port, QThread* thread)
{
    this->moveToThread(thread);
    tcpServer = new QTcpServer(thread);
    bool ok = tcpServer->listen(QHostAddress::Any, port);
    if (!ok) {
        qDebug() << "[S] server launch failed. attempted to listen port" << port;
        return false;
    }
    connect(tcpServer, &QTcpServer::newConnection, this, &GameServer::newClientConnection,
            Qt::DirectConnection);
    qDebug("[S] server start listening...");
    return ok;
}

/*
void GameServer::run()
{
    qDebug() << "[S] current thread:" << QThread::currentThread();
    tcpServer = new QTcpServer;
    bool ok = tcpServer->listen(QHostAddress::Any, port);
    if (!ok) {
        qDebug() << "[S] server launch failed. attempted to listen port" << port;
        return;
    }
    connect(tcpServer, &QTcpServer::newConnection, this, &GameServer::newClientConnection);
    qDebug("[S] server start listening...");
    exec();
}
*/

void GameServer::newClientConnection()
{
    bool accept = sockets.size() < maximumPlayers;

    QTcpSocket *socket = tcpServer->nextPendingConnection();

    if (accept) {
        sendMsg(socket, MSG_STRING, "Hello from server");

        sockets.insert(socket);
        qDebug() << "[S] accepted socket from " << socket->peerAddress() << ":" << socket->peerPort();
        // setup connections
        connect(socket, &QTcpSocket::disconnected, [this, socket](){ onClientDisconnected(socket); });
        connect(socket, &QTcpSocket::readyRead, [this, socket](){ onReadyRead(socket); });
    } else {
        sendMsgW(socket, MSG_REJECT);
    }

    if (state == GameState::waiting && sockets.size() == maximumPlayers) {
        qDebug("[S] broadcasting MSG_GAME_START");
        broadcast(MSG_GAME_START);
        state = GameState::confirming;
    }
}

void GameServer::onClientDisconnected(QTcpSocket *socket)
{
    sockets.erase(socket);
    qDebug() << "[S] socket" << socket << "disconnected.";
    if (state != GameState::waiting && !exiting) {
        QString msg = "client " + socket->peerAddress().toString() + ":" +
                QString::number(socket->peerPort()) + " shutdown";
        broadcast(MSG_GAME_QUIT, msg);
        exiting = true;
    }
}

void GameServer::onReadyRead(QTcpSocket *socket)
{
    QByteArray s;
    Message m = recvMsg(socket, &s);

    action(socket, m, s);
}

void GameServer::action(QTcpSocket* socket, Message& m, const QByteArray& s)
{
    if (m.type & MSG_STRING)
        qDebug() << "[S] message from" << socket << ":" << s;

    if ((m.type & MSG_ACK_GAME) && state == GameState::confirming) {
        // determine the order of players
        if (numPlayers < maximumPlayers) {
            qDebug() << "[S] map" << numPlayers << "<->" << socket->peerPort();
            players[numPlayers++] = socket;
            if (numPlayers == maximumPlayers) {
                distributeCards();

                // enter preempting state
                state = GameState::preempting;
                currentPlayer = 0;
                sendMsg(players[0], MSG_QUERY_PREEMPT, makeMessage(0));
            }
        }
    }

    if ((m.type & MSG_REPLY_PREEMPT) && state == GameState::preempting) {
        // record player actions (preemption)
        if (m.data > 0)
            preempted[currentPlayer] = currentPlayer + 1;
        else
            preempted[currentPlayer] = 0;
        // broadcast
        for (int i = 0; i < numPlayers; ++i)
            sendMsg(players[i], MSG_GAME_INFO, makeMessage(i));

        if (++currentPlayer >= maximumPlayers) {
            // all player actions are available
            determineLandlord();
            // give extra 3 cards to landlord
            for (Card card : remainingCards)
                cards[landlord].push_back(card);
            std::sort(cards[landlord].begin(), cards[landlord].end(), std::greater<Card>());

            // go to next state
            state = GameState::started;
            currentPlayer = landlord;
            for (int i = 0; i < maximumPlayers; ++i) {
                if (i == currentPlayer)
                    sendMsg(players[i], MSG_REQUEST_POST, makeMessage(i), 1);
                else
                    sendMsg(players[i], MSG_GAME_INFO, makeMessage(i));
            }
        } else {
            // query player action
            sendMsg(players[currentPlayer], MSG_QUERY_PREEMPT, makeMessage(currentPlayer));
        }
    }

    if ((m.type & MSG_POST_CARDS) && state == GameState::started) {
        cardsPosted[currentPlayer] = m.data;
        if (m.data) { // cards posted
            memset(cardsPosted, -1, sizeof(cardsPosted));
            lastPostedPlayer = currentPlayer;
            postedCardsRepr = s;
            for (int i = 0; i < postedCardsRepr.size(); ++i) {
                Card card = Card::fromChar(postedCardsRepr[i]);
                auto it = std::find(cards[currentPlayer].begin(), cards[currentPlayer].end(),
                                    card);
                assert(it != cards[currentPlayer].end());
                cards[currentPlayer].erase(it);
            }
        }

        if (cards[currentPlayer].empty()) {
            // game over
            broadcast(MSG_GAME_OVER, currentPlayer);

            // reset server state
            state = GameState::confirming;
            resetState();
            return;
        }

        for (int i = 0; i < maximumPlayers; ++i)
            sendMsg(players[i], MSG_GAME_INFO, makeMessage(i));

        currentPlayer = (currentPlayer + 1) % maximumPlayers;
        bool mustPost = false;
        if (lastPostedPlayer == currentPlayer) {
            // other players haven't posted cards, start a new round
            postedCardsRepr = "";
            mustPost = true;
        }
        sendMsg(players[currentPlayer], MSG_REQUEST_POST, makeMessage(currentPlayer), mustPost);
    }
}

void GameServer::distributeCards()
{
    generateCards();
    for (int i = 0; i < maximumPlayers; ++i) {
        sendMsg(players[i], MSG_GAME_INFO, makeMessage(i));
    }
}

void GameServer::broadcast(MessageType type, qint32 data)
{
    Message m = {.status = MSG_OK, .type = type, .length = 0, .data = data};
    for (auto socket : sockets) {
        sendMsg(socket, &m, sizeof(m));
    }
}

void GameServer::broadcast(MessageType type, const QString& str)
{
    QByteArray ba = str.toUtf8();
    Message m = {.status = MSG_OK, .type = type | MSG_STRING, .length = ba.size(), .data = 0};
    for (auto socket : sockets) {
        socket->write(reinterpret_cast<char*>(&m), sizeof(m));
        socket->write(ba.data(), ba.size());
    }
}

static inline QString cardsRepresentation(const QList<Card>& cards)
{
    QString res = "";
    for (auto c : cards)
        res += c.repr();
    return res;
}

QByteArray GameServer::makeMessage(int player) const
{
    QJsonObject root;
    // cards
    root["hcards"] = cardsRepresentation(cards[player]);
    root["rcards"] = cardsRepresentation(remainingCards);
    root["pcards"] = QString(postedCardsRepr);

    // player id
    root["id"] = player;
    // last player who posted cards
    root["last"] = lastPostedPlayer;
    // landlord
    root["landlord"] = landlord;
    // preemption?
    QJsonArray p;
    for (int i = 0; i < maximumPlayers; ++i)
        p.push_back(preempted[i]);
    root["preempt"] = p;
    // cards posted?
    QJsonArray cp;
    for (int i = 0; i < maximumPlayers; ++i)
        cp.push_back(cardsPosted[i]);
    root["posted"] = cp;
    // cards count
    QJsonArray cnt;
    for (int i = 0; i < maximumPlayers; ++i)
        cnt.push_back(cards[i].size());
    root["count"] = cnt;

    QJsonDocument doc;
    doc.setObject(root);
    return doc.toJson(QJsonDocument::Compact);
}

void GameServer::generateCards()
{
    QList<Card> fullCards;
    for (int v = 3; v <= 15; ++v) {
        for (int c = 0; c < 4; ++c)
            fullCards.push_back(v);
    }
    fullCards.push_back(16); // Joker1
    fullCards.push_back(17); // Joker2

    // shuffle
    for (int i = fullCards.size() - 1; i >= 0; --i)
        std::swap(fullCards[i], fullCards[qrand() % (i + 1)]);

    for (int i = 0; i < maximumPlayers; ++i) {
        for (int j = 0; j < 17; ++j)
            cards[i].push_back(fullCards[i * 17 + j]);
        std::sort(cards[i].begin(), cards[i].end(), std::greater<Card>());
    }

    remainingCards.push_back(fullCards[51]);
    remainingCards.push_back(fullCards[52]);
    remainingCards.push_back(fullCards[53]);
    std::sort(remainingCards.begin(), remainingCards.end(), std::greater<Card>());
}

int GameServer::determineLandlord()
{
    int *p = preempted;
    if (p[1])
        landlord = p[2] ? 2 : 1;
    else
        landlord = p[2] ? 2 : 0;
    return landlord;
}

GameServer::~GameServer()
{
    if (tcpServer) {
        if (tcpServer->isListening())
            tcpServer->close();
        delete tcpServer;
        tcpServer = nullptr;
    }
}
